package proxy

import (
	"github.com/fatih/color"
	"golang.org/x/crypto/ssh"
	"gosh/pkg/connect"
	"net"
	"os"
	"time"
)

func remoteProxy() {
	host := connect.NewHost(ProxyConfig.Host, ProxyConfig.User, ProxyConfig.Password, ProxyConfig.KeyContent,
		ProxyConfig.KeyPassphrase, ProxyConfig.Port, jumpServer, ProxyConfig.ConnectTimeout)

	err := host.Open()
	if err != nil {
		color.Red("[%s] open ssh connection failed,err:%s\n", time.Now().Format("15:04:05"), err.Error())
		os.Exit(1)
	}

	stop := make(chan bool, 1)
	errMsg := make(chan error, 1)
	go func() {
		for i := 0; i < 5; i++ {
			time.Sleep(time.Second)
			_, err = host.SSHClient.Dial(ProxyConfig.Protocol, ProxyConfig.RemoteAddress)
			if err == nil {
				color.Green("[%s] proxy connection is ready,please access remote address %s\n",
					time.Now().Format("15:04:05"), ProxyConfig.RemoteAddress)
				break
			}
		}
		if err != nil {
			stop <- true
			color.Red("[%s] proxy connection failed\n", time.Now().Format("15:04:05"))
			return
		}
	}()
	remoteForward(host.SSHClient, ProxyConfig.Protocol, ProxyConfig.LocalAddress, ProxyConfig.RemoteAddress, stop, errMsg)
}

// 这里面涉及的channel让gc进行回收，手动关闭容易出现panic
// 端口转发
func remoteForward(client *ssh.Client, protocol, localAddr, remoteAddr string, stop chan bool, errMsg chan error) {
	// 打开远程端口
	listener, err := client.Listen(protocol, remoteAddr)
	if err != nil {
		color.Red("[%s] open remote listen address %s failed,err:%s\n", time.Now().Format("15:04:05"), ProxyConfig.RemoteAddress, err.Error())
		return
	}
	defer func() { _ = listener.Close() }()
	// 定义异常退出机制，因为设置了 stop chan，为了避免在stop chan阻塞，引入err chan，两者满足其一就能退出
	var errChan = make(chan error, 5)

	// 循环接收远程端口的请求
	go func() {
		for {
			remoteConn, err := listener.Accept()
			if remoteConn == nil {
				errMsg <- err
				continue
			}
			if err != nil {
				_ = remoteConn.Close()
				errMsg <- err
				continue
			}
			go establishRemote(protocol, localAddr, remoteConn, errChan)
		}
	}()

	for {
		select {
		case <-stop:
			color.Green("[%s] receive stop signal,process exit\n", time.Now().Format("15:04:05"))
			return
		case err = <-errMsg:
			if err != nil {
				color.Red("[%s] open remote listen address %s failed,err:%s\n", time.Now().Format("15:04:05"), ProxyConfig.RemoteAddress, err.Error())
			}
			return
		case err = <-errChan:
			if err != nil {
				color.Yellow("[%s] accept request failed, err:%s\n", time.Now().Format("15:04:05"), err.Error())
			}
		}
	}

}

// 处理本地端口的请求
func establishRemote(protocol, localAddress string, remote net.Conn, errChan chan error) {
	// 打开本地的端口, 每次接收一个新的TCP连接，都得开一次远程转发
	defer remote.Close()
	local, err := net.Dial(protocol, localAddress)
	if err != nil {
		errChan <- err
		return
	}
	defer local.Close()
	defer func() { _ = local.Close() }()
	errCh := make(chan error, 1)
	go exchangeData(local, remote, errCh)
	go exchangeData(remote, local, errCh)
	<-errCh
	<-errCh
}
